
#pragma once

#include <random>
#include <string>
#include <assert.h>
#include <sstream>
#include <vector>
#include <set>
#include <algorithm>

#include <windows.h>

typedef std::set<int> FuzzyScore;

class Card {
	

public:
	enum Color {
		Spades, Clubs, Heart, Diamond
	};

	enum Face	{
		Ace, King, Queen, Jack,
		Number10,
		Number9,
		Number8,
		Number7,
		Number6,
		Number5,
		Number4,
		Number3,
		Number2
	};
private:
	Color m_color;
	Face m_face;
public:
	Card(Color color, Face face) : m_color(color), m_face(face) {}

	std::string toString();

	Face getFace() const
	{
		return m_face;
	}

	Color getColor() const
	{
		return m_color;
	}

	FuzzyScore getScore() const {
		FuzzyScore s;
		switch (m_face)
		{
		case Card::Ace:
			s.insert(1); s.insert(11);
			break;
		case Card::King:
		case Card::Queen:
		case Card::Jack:
		case Card::Number10:
			s.insert(10);
			break;
		case Card::Number9:
			s.insert(9);
			break;
		case Card::Number8:
			s.insert(8);
			break;
		case Card::Number7:
			s.insert(7);
			break;
		case Card::Number6:
			s.insert(6);
			break;
		case Card::Number5:
			s.insert(5);
			break;
		case Card::Number4:
			s.insert(4);
			break;
		case Card::Number3:
			s.insert(3);
			break;
		case Card::Number2:
			s.insert(2);
			break;
		default:
			assert(false);
			break;
		}
		return s;
	}

};


inline std::ostream& operator<< (std::ostream& s, const Card& c) {
	switch (c.getFace())
	{
	case Card::Ace: s << "A"; break;
	case Card::King: s << "K"; break;
	case Card::Queen: s << "Q"; break;
	case Card::Jack: s << "J"; break;
	case Card::Number10:
	case Card::Number9:
	case Card::Number8:
	case Card::Number7:
	case Card::Number6:
	case Card::Number5:
	case Card::Number4:
	case Card::Number3:
	case Card::Number2:
		s << (10-(c.getFace()-Card::Number10));
		break;
	default:
		assert(false);
		break;
	}

	switch (c.getColor())
	{
	case Card::Spades: s<<"S"; break;
	case Card::Clubs:s<<"C"; break;
	case Card::Heart: s<<"H"; break;
	case Card::Diamond: s<<"D"; break;
	default:
		assert(false);
		break;
	}
	return s;
}

inline std::string Card::toString() {
	std::stringstream s;
	s << *this;
	return s.str();
}

inline FuzzyScore AddScore(int a, FuzzyScore& b) {
	FuzzyScore s;
	for(auto it = b.begin(), end = b.end(); it!=end; ++it) {
		s.insert( a + *it );
	}
	return s;
}

inline FuzzyScore AddScore(FuzzyScore& a, FuzzyScore& b) {
	FuzzyScore s;
	for(auto it = a.begin(), end = a.end(); it!=end; ++it) {
		FuzzyScore ss = AddScore(*it, b);
		s.insert( ss.begin(), ss.end() );
	}
	return s;
}

class HandOfCard {
	std::vector<Card> m_visible_cards;
	std::vector<Card> m_hidden_cards;
public:
	typedef std::vector<Card>::const_iterator ConstCardIterator;
	int numHiddenCard() const { return m_hidden_cards.size(); }
	const ConstCardIterator beginVisibleCard() const { return m_visible_cards.begin(); }
	const ConstCardIterator endVisibleCard() const { return m_visible_cards.end(); }

	void AddCard(Card c, bool visible) {
		if(visible)
			m_visible_cards.push_back(c);
		else
			m_hidden_cards.push_back(c);
	}

private:
	void SumVectorOfCard(FuzzyScore& score, const std::vector<Card>& c) {
		for(ConstCardIterator it = c.begin(), end = c.end(); it!=end; ++it) {
			FuzzyScore cs = it->getScore();
			score = AddScore(score, cs);
		}
	}
public:
	int computeScore() {
		FuzzyScore sum;
		sum.insert(0);
		SumVectorOfCard(sum, m_visible_cards);
		SumVectorOfCard(sum, m_hidden_cards);
		FuzzyScore::iterator it = sum.begin();
		int prev_value = *it;
		while( it !=sum.end() &&  *it <= 21 ) {
			prev_value = *it;
			++it;
		}
		return prev_value;
	} 

};

inline std::ostream& operator<< (std::ostream& s, const HandOfCard& c) {
	for(HandOfCard::ConstCardIterator it = c.beginVisibleCard(), end = c.endVisibleCard(); it!=end; ++it) {
		s << *it << " " ;
	}
	for(int i=0; i<c.numHiddenCard(); ++i)
		s << "?? " ;
	return s;
}

class CardStack {
	std::mt19937 m_random_generator;
	static const int m_card_per_color = 13;
	static const int m_num_colors = 4;
public:
	CardStack() {
		m_random_generator.seed( timeGetTime() );
	}
	Card getNextCard() {
		std::uniform_int<> distribution(0, m_card_per_color*m_num_colors-1);
		const int cardid = distribution(m_random_generator);
		const int colorid = cardid%m_num_colors;
		const int faceid = cardid/m_num_colors;
		return Card( Card::Color(colorid), Card::Face(faceid) );
	}
};
